/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <glib.h>

#include "display.h"
#include "xqueue.h"

#define MINSZ 16
static XEvent *q = NULL;
static gulong qsz = 0;
static gulong qstart;
static gulong qend;
static gulong qnum = 0;
static inline void shrink(void)
{
	if(qsz > MINSZ && qnum < qsz / 4) {
		const gulong newsz = qsz / 2;
		gulong i;
		if(qnum == 0) {
			qstart = 0;
			qend = -1;
		} else if(qstart >= newsz && qend >= newsz) {
			for(i = 0; i < qnum; ++i)
				q[i] = q[qstart + i];
			qstart = 0;
			qend = qnum - 1;
		} else if(qstart >= newsz) {
			const gulong n = qsz - qstart;
			for(i = 0; i < n; ++i)
				q[newsz - n + i] = q[qstart + i];
			qstart = newsz - n;
		} else if(qend >= newsz) {
			const gulong n = qend + 1 - newsz;
			for(i = 0; i < n; ++i)
				q[i] = q[newsz + i];
			qend = n - 1;
		}
		q = g_renew(XEvent, q, newsz);
		qsz = newsz;
	}
}

static inline void grow(void)
{
	if(qnum == qsz) {
		const gulong newsz = qsz * 2;
		gulong i;
		q = g_renew(XEvent, q, newsz);
		g_assert(qnum > 0);
		if(qend < qstart) {
			for(i = 0; i <= qend; ++i)
				q[qsz + i] = q[i];
			qend = qsz + qend;
		}
		qsz = newsz;
	}
}

static gboolean read_events(gboolean block)
{
	gint sth, n;
	n = XEventsQueued(t_display, QueuedAfterFlush) > 0;
	sth = FALSE;
	while((block && !sth) || n > 0) {
		XEvent e;
		if(XNextEvent(t_display, &e) != Success)
			return FALSE;
		grow();
		++qnum;
		qend = (qend + 1) % qsz;
		q[qend] = e;
		--n;
		sth = TRUE;
	}
	return sth;
}

static void pop(const gulong p)
{
	--qnum;
	if(qnum == 0) {
		qstart = 0;
		qend = -1;
	} else if(p == qstart)
		qstart = (qstart + 1) % qsz;
	else {
		gulong pi;
		if((p >= qstart && p < qstart + qnum / 2) || (p < qstart && p < (qstart + qnum / 2) % qsz)) {
			pi = p;
			while(pi != qstart) {
				const gulong pi_next = (pi == 0 ? qsz - 1 : pi - 1);
				q[pi] = q[pi_next];
				pi = pi_next;
			}
			qstart = (qstart + 1) % qsz;
		} else {
			pi = p;
			while(pi != qend) {
				const gulong pi_next = (pi + 1) % qsz;
				q[pi] = q[pi_next];
				pi = pi_next;
			}
			qend = (qend == 0 ? qsz - 1 : qend - 1);
		}
	}
	shrink();
}

void xqueue_init(void)
{
	if(q != NULL)
		return;
	qsz = MINSZ;
	q = g_new(XEvent, qsz);
	qstart = 0;
	qend = -1;
}

void xqueue_destroy(void)
{
	if(q == NULL)
		return;
	g_free(q);
	q = NULL;
	qsz = 0;
}

gboolean xqueue_match_type(XEvent *e, gpointer data)
{
	return e->type == GPOINTER_TO_INT(data);
}

gboolean xqueue_match_window_type(XEvent *e, gpointer data)
{
	const struct wm_xqueue_window_type x = *(struct wm_xqueue_window_type *)data;
	return e->xany.window == x.window && e->type == x.type;
}

static gboolean next_local(XEvent *event_return)
{
	g_return_val_if_fail(q != NULL, FALSE);
	g_return_val_if_fail(event_return != NULL, FALSE);
	if(!qnum)
		read_events(FALSE);
	if(qnum) {
		*event_return = q[qstart];
		pop(qstart);
		return TRUE;
	}
	return FALSE;
}

gboolean xqueue_exists(xqueue_match_func match, gpointer data)
{
	gulong i, checked;
	g_return_val_if_fail(q != NULL, FALSE);
	g_return_val_if_fail(match != NULL, FALSE);
	checked = 0;
	while(TRUE) {
		for(i = checked; i < qnum; ++i, ++checked) {
			const gulong p = (qstart + i) % qsz;
			if(match(&q[p], data))
				return TRUE;
		}
		if(!read_events(TRUE))
			break;
	}
	return FALSE;
}

gboolean xqueue_exists_local(xqueue_match_func match, gpointer data)
{
	gulong i, checked;
	g_return_val_if_fail(q != NULL, FALSE);
	g_return_val_if_fail(match != NULL, FALSE);
	checked = 0;
	while(TRUE) {
		for(i = checked; i < qnum; ++i, ++checked) {
			const gulong p = (qstart + i) % qsz;
			if(match(&q[p], data))
				return TRUE;
		}
		if(!read_events(FALSE))
			break;
	}
	return FALSE;
}

gboolean xqueue_remove_local(XEvent *event_return, xqueue_match_func match, gpointer data)
{
	gulong i, checked;
	g_return_val_if_fail(q != NULL, FALSE);
	g_return_val_if_fail(event_return != NULL, FALSE);
	g_return_val_if_fail(match != NULL, FALSE);
	checked = 0;
	while(TRUE) {
		for(i = checked; i < qnum; ++i, ++checked) {
			const gulong p = (qstart + i) % qsz;
			if(match(&q[p], data)) {
				*event_return = q[p];
				pop(p);
				return TRUE;
			}
		}
		if(!read_events(FALSE))
			break;
	}
	return FALSE;
}

struct xqueue_cb {
	wm_xqueue_func func;
	gpointer data;
};
static struct xqueue_cb *callbacks = NULL;
static guint n_callbacks = 0;
static gboolean event_read(GSource *source, GSourceFunc callback, gpointer data)
{
	XEvent ev;
	(void)source;
	(void)callback;
	(void)data;
	while(next_local(&ev)) {
		guint i;
		for(i = 0; i < n_callbacks; ++i)
			callbacks[i].func(&ev, callbacks[i].data);
	}
	return TRUE;
}

static gboolean x_source_prepare(GSource *source, gint *timeout)
{
	(void)source;
	*timeout = -1;
	return XPending(t_display);
}

static gboolean x_source_check(GSource *source)
{
	(void)source;
	return XPending(t_display);
}

struct x_source {
	GSource source;
	GPollFD pfd;
};
static GSourceFuncs x_source_funcs = {
	x_source_prepare,
	x_source_check,
	event_read,
	NULL
};

void xqueue_listen(void)
{
	GSource *source = g_source_new(&x_source_funcs, sizeof(struct x_source));
	struct x_source *x_source = (struct x_source *)source;
	GPollFD *pfd = &x_source->pfd;
	*pfd = (GPollFD) {
	ConnectionNumber(t_display), G_IO_IN, G_IO_IN};
	g_source_add_poll(source, pfd);
	g_source_attach(source, NULL);
}

void xqueue_add_callback(wm_xqueue_func f, gpointer data)
{
	guint i;
	g_return_if_fail(f != NULL);
	for(i = 0; i < n_callbacks; ++i)
		if(callbacks[i].func == f && callbacks[i].data == data)
			return;
	callbacks = g_renew(struct xqueue_cb, callbacks, n_callbacks + 1);
	callbacks[n_callbacks].func = f;
	callbacks[n_callbacks].data = data;
	++n_callbacks;
}
